package com.apes.fico.engine.timerTask;

import com.apes.fico.engine.model.ReduceVoucher;
import com.apes.fico.engine.model.TempVoucher;
import com.apes.fico.engine.repository.ReduceVoucherRepository;
import com.apes.fico.engine.repository.TempVoucherRepository;
import com.apes.fico.masterdata.gl.model.AccountingModel;
import com.apes.framework.api.SpringManager;
import com.apes.framework.plugin.esb.api.SimpleRequest;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;

@Service("finTimerTaskService")
public class TimerTaskService {


    @Autowired
    TempVoucherRepository tempVoucherRepository;

    @Autowired
    ReduceVoucherRepository reduceVoucherRepository;

    @Autowired
    TransactionTemplate transactionTemplate;

    @Autowired
    Collection<Reduce> reduces;



    /*
    * 原始凭证定时归约
    * */
    public void reduceForRealTime(SimpleRequest request){
        String maxId = tempVoucherRepository.findMaxIdByZeroState();
        Function<Integer, List<String>> function = (size) -> tempVoucherRepository.findFalseStateTempVouchersByIdLessThanEqual(maxId,PageRequest.of(0, size));
        PageLoop pageLoop = new PageLoop(1000,function,this::reduce);
    }

    /*
    * 按天凭证模板
    * */
    public void toGLVoucherForDay(SimpleRequest request) {
        String cycleValue;
        Object date = request.getMap().get("date");
        if(date != null) {
            LocalDate localDate = DateUtil.parse((String) date, "yyyyMMdd");
            cycleValue = DateUtil.format(localDate,"yyyyMMdd");
        }else {
            cycleValue = DateUtil.format(LocalDate.now().minusDays(1),"yyyyMMdd");
        }
        Function<Integer, List<String>> function = (size) -> reduceVoucherRepository.findNotDoneAndNotErrorVoucherByCycleAndCycleValue(AccountingModel.D,cycleValue,PageRequest.of(0, size));
        PageLoop pageLoop = new PageLoop(1000,function,this::toGLVoucher);
    }

    /*
    * 按月凭证模板
    * */
    public void toGLVoucherForMonth(SimpleRequest request) {
        String cycleValue;
        Object month = request.getMap().get("month");
        if(month != null) {
            YearMonth yearMonth = YearMonth.parse((String)month, DateTimeFormatter.ofPattern("yyyyMM"));
            cycleValue = yearMonth.format(DateTimeFormatter.ofPattern("yyyyMM"));
        }else {
            /*定时任务每月1号*/
            LocalDate firstDate = LocalDate.now().minus(1, ChronoUnit.MONTHS);
            cycleValue = DateUtil.format(firstDate,"yyyyMM");
        }
        Function<Integer, List<String>> function = (size) -> reduceVoucherRepository.findNotDoneAndNotErrorVoucherByCycleAndCycleValue(AccountingModel.M,cycleValue,PageRequest.of(0, size));
        PageLoop pageLoop = new PageLoop(1000,function,this::toGLVoucher);
    }



    /*
    * 归约
    * */
    public void reduce(String tempVoucherId) {
        TempVoucher tempVoucher = tempVoucherRepository.findOne(tempVoucherId);
        String cycle = tempVoucher.getVoucherTemplate().getCycle();
        reduces.stream().filter((item)-> item.correctCycle(cycle)).findFirst().get().reduce(tempVoucher);
    }



    /*
    * 生成财务凭证并自动审核
    * */
    public void toGLVoucher(String  reduceVoucherID) {
        ReduceVoucher reduceVoucher = reduceVoucherRepository.findOne(reduceVoucherID);
        String glVoucherId = SpringManager.getBean(BasisService.class).createGLVoucherAndApprove(reduceVoucher);
        reduceVoucher.setDone(true);
        reduceVoucher.setVoucherId(glVoucherId);
        reduceVoucherRepository.saveAndFlush(reduceVoucher);
    }



    @Data
    public class PageLoop{
        private int size;
        private Function<Integer, List<String>> pagesFunction;
        private Consumer<String> toDoById;

        public PageLoop(int size, Function<Integer, List<String>> getPages,Consumer<String> toDoById) {
            this.size = size > 0 ? size : 1000;
            pagesFunction = getPages;
            this.toDoById = toDoById;
            loop();
        }

        private void loop() {
            int lastSize = size;
            List<String> pages = new ArrayList<String>();
            int error; // 避免无限循环
            Set<String> errorIds = new HashSet<>(); //记录错误id
            int count = 0; // 限制最长循环次数
            String uniqueIds;
            String lastUniqueIds = "";
            loop: while(lastSize == size) {
                try{
                    pages = this.pagesFunction.apply(size);
                }catch (Exception e) {
                    break loop;
                }
                lastSize = pages.size();
                if(lastSize == size) { // 避免每次查询结果一样
                    uniqueIds = pages.get(0) + pages.get(lastSize-1);
                    if(lastUniqueIds.equals(uniqueIds)) {
                        break loop;
                    }else {
                        lastUniqueIds = uniqueIds;
                    }
                }
                error = 0;
                count += 1;
                if(count == 2000) break loop;
                for(String id:pages) {
                    try {
                        if(errorIds.contains(id)) { error += 1; continue;}
                        transactionTemplate.execute(status -> {
                            toDoById.accept(id);
                            return null;
                        });
                    }catch (Exception e) {
                        errorIds.add(id);
                        error += 1;
                        if(error == size) break loop;
                    }
                }
            }
        }

    }
}



